package com.gxar.navigation.server.db.service;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists;
import com.gxar.common.entity.PageResult;
import com.gxar.common.entity.Result;
import com.gxar.common.exception.APIRuntimeException;
import com.gxar.common.utils.PageUtils;
import com.gxar.navigation.server.base.common.constant.MapConstant;
import com.gxar.navigation.server.base.common.utils.NearestPointLineCalculateUtils;
import com.gxar.navigation.server.base.model.ResultCodeEnum;
import com.gxar.navigation.server.base.model.arithmetic.dto.PointDTO;
import com.gxar.navigation.server.base.model.line.mapper.LineMapStruct;
import com.gxar.navigation.server.base.model.line.vo.request.LineCreateRequest;
import com.gxar.navigation.server.base.model.line.vo.request.LineUpdateRequest;
import com.gxar.navigation.server.base.model.line.vo.response.LineCreateResponse;
import com.gxar.navigation.server.base.model.line.vo.response.LineInfoResponse;
import com.gxar.navigation.server.base.model.line.vo.response.LinePageResponse;
import com.gxar.navigation.server.base.model.line.vo.response.LineUpdateResponse;
import com.gxar.navigation.server.base.model.node.vo.LocationVO;
import com.gxar.navigation.server.db.mapper.LineMapper;
import com.gxar.navigation.server.db.mapper.NodeMapper;
import com.gxar.navigation.server.db.model.LineEntity;
import com.gxar.navigation.server.db.model.NodeEntity;
import com.gxar.navigation.server.db.repository.LineRepository;
import com.gxar.navigation.server.db.repository.NodeRepository;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author linsy
 * @version 1.0
 * @created 2023/9/12 15:50
 */
@Service
@RequiredArgsConstructor
public class LineService {

    private final NodeMapper nodeMapper;
    private final NodeRepository nodeRepository;

    private final LineMapper lineMapper;
    private final LineRepository lineRepository;

    @Transactional(rollbackFor = Throwable.class)
    public LineCreateResponse create(Long activeId, LineCreateRequest lineCreateRequest) {
        // 起点
        LocationVO origin = lineCreateRequest.getOrigin();
        // 终点
        LocationVO destination = lineCreateRequest.getDestination();
        NodeEntity oriNode = this.verifyNode(origin);
        NodeEntity desNode = this.verifyNode(destination);

        LineEntity oldLineEntity = lineMapper.findByOriAndDes(oriNode.getId(), desNode.getId());
        if (Objects.nonNull(oldLineEntity)) {
            throw new APIRuntimeException(ResultCodeEnum.LINE_EXIST);
        }
        LineEntity lineEntity = LineMapStruct.INSTANCE.createRequestToEntity(lineCreateRequest);
        lineEntity.setOrigin(oriNode.getId());
        lineEntity.setDestination(desNode.getId());
        if (Objects.isNull(lineEntity.getDistance())) {
            Double distance = NearestPointLineCalculateUtils.calculateHaversineDistance(
                    new PointDTO(1L, origin.getLatitude(), origin.getLongitude()),
                    new PointDTO(2L, desNode.getLatitude(), desNode.getLongitude()));
            if (distance.compareTo(0d) < 0) {
                throw new APIRuntimeException(ResultCodeEnum.LINE_CALCULATE_DISTANCE_NOT_POSITIVE_NUMBER);
            }
            lineEntity.setDistance(BigDecimal.valueOf(distance).multiply(BigDecimal.valueOf(MapConstant.DISTANCE_CONVERSION)).longValue());
        } else {
            lineEntity.setDistance(BigDecimal.valueOf(lineEntity.getDistance()).multiply(BigDecimal.valueOf(MapConstant.DISTANCE_CONVERSION)).longValue());
        }
        lineEntity.setActiveId(activeId);
        LineEntity saved = lineRepository.save(lineEntity);
        LineCreateResponse lineCreateResponse = LineMapStruct.INSTANCE.entityToCreateResponse(saved);
        lineCreateResponse.setOrigin(origin);
        lineCreateResponse.setDestination(destination);
        return lineCreateResponse;
    }

    public Result<PageResult<LinePageResponse>> pageList(Long activeId, Integer pageNum, Integer pageSize) {
        Page<Object> page = PageHelper.startPage(pageNum, pageSize);
        List<LineEntity> lineEntities = lineMapper.queryPage(activeId);
        List<LinePageResponse> nodePageList = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(lineEntities)) {
            List<Long> nodeIds = new ArrayList<>();
            nodeIds.addAll(lineEntities.stream().map(LineEntity::getOrigin).collect(Collectors.toList()));
            nodeIds.addAll(lineEntities.stream().map(LineEntity::getDestination).collect(Collectors.toList()));
            Map<Long, NodeEntity> nodeEntityMap = this.queryNode(activeId, nodeIds);
            for (LineEntity lineEntity : lineEntities) {
                LinePageResponse linePageResponse = LineMapStruct.INSTANCE.entityToPageResponse(lineEntity);
                // 起点
                NodeEntity oriNode = nodeEntityMap.get(lineEntity.getOrigin());
                if (Objects.nonNull(oriNode)) {
                    linePageResponse.setOrigin(new LocationVO(oriNode.getLongitude(), oriNode.getLatitude()));
                }
                // 终点
                NodeEntity desNode = nodeEntityMap.get(lineEntity.getDestination());
                if (Objects.nonNull(desNode)) {
                    linePageResponse.setDestination(new LocationVO(desNode.getLongitude(), desNode.getLatitude()));
                }
                nodePageList.add(linePageResponse);
            }
        }
        PageInfo<LinePageResponse> nodePageInfo = new PageInfo<>(nodePageList);
        nodePageInfo.setTotal(page.getTotal());
        return PageUtils.getPageResult(nodePageInfo, nodePageList);
    }

    public LineInfoResponse getDetail(Long activeId, Long id) {
        Optional<LineEntity> lineEntityOptional = lineRepository.findById(id);
        if (!lineEntityOptional.isPresent()) {
            return null;
        }
        LineEntity oriLineEntity = lineEntityOptional.get();
        if (!oriLineEntity.getActiveId().equals(activeId)) {
            throw new APIRuntimeException(ResultCodeEnum.LINE_NOT_EXIST);
        }
        LineEntity lineEntity = lineEntityOptional.get();
        LineInfoResponse lineInfoResponse = LineMapStruct.INSTANCE.entityToInfoResponse(lineEntity);
        Map<Long, NodeEntity> nodeEntityMap = this.queryNode(activeId, Lists.newArrayList(lineEntity.getOrigin(), lineEntity.getDestination()));
        NodeEntity oriNode = nodeEntityMap.get(lineEntity.getOrigin());
        if (Objects.nonNull(oriNode)) {
            lineInfoResponse.setOrigin(new LocationVO(oriNode.getLongitude(), oriNode.getLatitude()));
        }
        NodeEntity desNode = nodeEntityMap.get(lineEntity.getDestination());
        if (Objects.nonNull(desNode)) {
            lineInfoResponse.setDestination(new LocationVO(desNode.getLongitude(), desNode.getLatitude()));
        }
        return lineInfoResponse;
    }

    @Transactional(rollbackFor = Throwable.class)
    public LineUpdateResponse update(Long activeId, Long id, LineUpdateRequest lineUpdateRequest) {
        Optional<LineEntity> lineEntityOptional = lineRepository.findById(id);
        if (!lineEntityOptional.isPresent()) {
            throw new APIRuntimeException(ResultCodeEnum.LINE_NOT_EXIST);
        }
        LineEntity oriLineEntity = lineEntityOptional.get();
        if (!oriLineEntity.getActiveId().equals(activeId)) {
            throw new APIRuntimeException(ResultCodeEnum.LINE_NOT_EXIST);
        }
        // 起点
        LocationVO origin = lineUpdateRequest.getOrigin();
        // 终点
        LocationVO destination = lineUpdateRequest.getDestination();
        NodeEntity oriNode = this.verifyNode(origin);
        NodeEntity desNode = this.verifyNode(destination);

        LineEntity lineEntity = LineMapStruct.INSTANCE.updateRequestToEntity(lineUpdateRequest);
        lineEntity.setOrigin(oriNode.getId());
        lineEntity.setDestination(desNode.getId());
        lineEntity.setId(oriLineEntity.getId());
        lineEntity.setActiveId(activeId);
        if (Objects.isNull(lineUpdateRequest.getDistance())) {
            Double distance = NearestPointLineCalculateUtils.calculateHaversineDistance(
                    new PointDTO(1L, oriNode.getLatitude(), oriNode.getLongitude()),
                    new PointDTO(2L, desNode.getLatitude(), desNode.getLongitude()));
            if (distance.compareTo(0d) < 0) {
                throw new APIRuntimeException(ResultCodeEnum.LINE_CALCULATE_DISTANCE_NOT_POSITIVE_NUMBER);
            }
            lineEntity.setDistance(BigDecimal.valueOf(distance).multiply(BigDecimal.valueOf(MapConstant.DISTANCE_CONVERSION)).longValue());
        } else {
            lineEntity.setDistance(BigDecimal.valueOf(lineEntity.getDistance()).multiply(BigDecimal.valueOf(MapConstant.DISTANCE_CONVERSION)).longValue());
        }
        LineEntity updated = lineRepository.save(lineEntity);
        LineUpdateResponse lineUpdateResponse = LineMapStruct.INSTANCE.entityToUpdateResponse(updated);
        lineUpdateResponse.setOrigin(origin);
        lineUpdateResponse.setDestination(destination);
        return lineUpdateResponse;
    }

    public Boolean delete(Long activeId, Long id) {
        Optional<LineEntity> lineEntityOptional = lineRepository.findById(id);
        if (!lineEntityOptional.isPresent()) {
            throw new APIRuntimeException(ResultCodeEnum.NODE_NOT_EXIST);
        }
        LineEntity oriLineEntity = lineEntityOptional.get();
        if (!oriLineEntity.getActiveId().equals(activeId)) {
            throw new APIRuntimeException(ResultCodeEnum.LINE_NOT_EXIST);
        }
        lineRepository.delete(lineEntityOptional.get());
        return true;
    }

    /**
     * 校验node是否存在，如果不存在则创建
     *
     * @param locationVo 位置信息
     * @return node
     */
    private NodeEntity verifyNode(LocationVO locationVo) {
        NodeEntity nodeEntity = nodeMapper.findByLonAndLat(locationVo.getLongitude(), locationVo.getLatitude());
        if (Objects.nonNull(nodeEntity)) {
            return nodeEntity;
        }
        nodeEntity = new NodeEntity();
        nodeEntity.setLongitude(locationVo.getLongitude());
        nodeEntity.setLatitude(locationVo.getLatitude());
        return nodeRepository.save(nodeEntity);
    }

    /**
     * 查询nodeMap集合
     *
     * @param activeId
     * @param nodeIds  nodeId
     * @return map集合
     */
    private Map<Long, NodeEntity> queryNode(Long activeId, List<Long> nodeIds) {
        List<NodeEntity> nodeEntityList = nodeMapper.findInId(activeId, nodeIds);
        return Optional.ofNullable(nodeEntityList).orElse(Collections.emptyList()).stream().collect(Collectors.toMap(NodeEntity::getId, t -> t, (t1, t2) -> t1));
    }
}
